/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;

using RAMOutputStream = Lucene.Net.Store.RAMOutputStream;
using ArrayUtil = Lucene.Net.Util.ArrayUtil;

namespace Lucene.Net.Index
{
    
    /// <summary>This is a DocFieldConsumer that writes stored fields. </summary>
    sealed class StoredFieldsWriter
    {
        private void  InitBlock()
        {
            docFreeList = new PerDoc[1];
        }
        
        internal FieldsWriter fieldsWriter;
        internal DocumentsWriter docWriter;
        internal FieldInfos fieldInfos;
        internal int lastDocID;
        
        internal PerDoc[] docFreeList;
        internal int freeCount;
        
        public StoredFieldsWriter(DocumentsWriter docWriter, FieldInfos fieldInfos)
        {
            InitBlock();
            this.docWriter = docWriter;
            this.fieldInfos = fieldInfos;
        }
        
        public StoredFieldsWriterPerThread AddThread(DocumentsWriter.DocState docState)
        {
            return new StoredFieldsWriterPerThread(docState, this);
        }
        
        public void  Flush(SegmentWriteState state)
        {
            lock (this)
            {
                
                if (state.numDocsInStore > 0)
                {
                    // It's possible that all documents seen in this segment
                    // hit non-aborting exceptions, in which case we will
                    // not have yet init'd the FieldsWriter:
                    InitFieldsWriter();
                    
                    // Fill fdx file to include any final docs that we
                    // skipped because they hit non-aborting exceptions
                    Fill(state.numDocsInStore - docWriter.DocStoreOffset);
                }
                
                if (fieldsWriter != null)
                    fieldsWriter.Flush();
            }
        }
        
        private void  InitFieldsWriter()
        {
            if (fieldsWriter == null)
            {
                System.String docStoreSegment = docWriter.DocStoreSegment;
                if (docStoreSegment != null)
                {
                    System.Diagnostics.Debug.Assert(docStoreSegment != null);
                    fieldsWriter = new FieldsWriter(docWriter.directory, docStoreSegment, fieldInfos);
                    docWriter.AddOpenFile(docStoreSegment + "." + IndexFileNames.FIELDS_EXTENSION);
                    docWriter.AddOpenFile(docStoreSegment + "." + IndexFileNames.FIELDS_INDEX_EXTENSION);
                    lastDocID = 0;
                }
            }
        }
        
        public void  CloseDocStore(SegmentWriteState state)
        {
            lock (this)
            {
                int inc = state.numDocsInStore - lastDocID;
                if (inc > 0)
                {
                    InitFieldsWriter();
                    Fill(state.numDocsInStore - docWriter.DocStoreOffset);
                }
                
                if (fieldsWriter != null)
                {
                    fieldsWriter.Dispose();
                    fieldsWriter = null;
                    lastDocID = 0;
                    System.Diagnostics.Debug.Assert(state.docStoreSegmentName != null);
                    state.flushedFiles.Add(state.docStoreSegmentName + "." + IndexFileNames.FIELDS_EXTENSION);
                    state.flushedFiles.Add(state.docStoreSegmentName + "." + IndexFileNames.FIELDS_INDEX_EXTENSION);
                    
                    state.docWriter.RemoveOpenFile(state.docStoreSegmentName + "." + IndexFileNames.FIELDS_EXTENSION);
                    state.docWriter.RemoveOpenFile(state.docStoreSegmentName + "." + IndexFileNames.FIELDS_INDEX_EXTENSION);
                    
                    System.String fileName = state.docStoreSegmentName + "." + IndexFileNames.FIELDS_INDEX_EXTENSION;
                    
                    if (4 + ((long) state.numDocsInStore) * 8 != state.directory.FileLength(fileName))
                        throw new System.SystemException("after flush: fdx size mismatch: " + state.numDocsInStore + " docs vs " + state.directory.FileLength(fileName) + " length in bytes of " + fileName + " file exists?=" + state.directory.FileExists(fileName));
                }
            }
        }
        
        internal int allocCount;
        
        internal PerDoc GetPerDoc()
        {
            lock (this)
            {
                if (freeCount == 0)
                {
                    allocCount++;
                    if (allocCount > docFreeList.Length)
                    {
                        // Grow our free list up front to make sure we have
                        // enough space to recycle all outstanding PerDoc
                        // instances
                        System.Diagnostics.Debug.Assert(allocCount == 1 + docFreeList.Length);
                        docFreeList = new PerDoc[ArrayUtil.GetNextSize(allocCount)];
                    }
                    return new PerDoc(this);
                }
                else
                    return docFreeList[--freeCount];
            }
        }
        
        internal void  Abort()
        {
            lock (this)
            {
                if (fieldsWriter != null)
                {
                    try
                    {
                        fieldsWriter.Dispose();
                    }
                    catch (System.Exception)
                    {
                    }
                    fieldsWriter = null;
                    lastDocID = 0;
                }
            }
        }
        
        /// <summary>Fills in any hole in the docIDs </summary>
        internal void  Fill(int docID)
        {
            int docStoreOffset = docWriter.DocStoreOffset;
            
            // We must "catch up" for all docs before us
            // that had no stored fields:
            int end = docID + docStoreOffset;
            while (lastDocID < end)
            {
                fieldsWriter.SkipDocument();
                lastDocID++;
            }
        }
        
        internal void  FinishDocument(PerDoc perDoc)
        {
            lock (this)
            {
                System.Diagnostics.Debug.Assert(docWriter.writer.TestPoint("StoredFieldsWriter.finishDocument start"));
                InitFieldsWriter();
                
                Fill(perDoc.docID);
                
                // Append stored fields to the real FieldsWriter:
                fieldsWriter.FlushDocument(perDoc.numStoredFields, perDoc.fdt);
                lastDocID++;
                perDoc.Reset();
                Free(perDoc);
                System.Diagnostics.Debug.Assert(docWriter.writer.TestPoint("StoredFieldsWriter.finishDocument end"));
            }
        }
        
        public bool FreeRAM()
        {
            return false;
        }
        
        internal void  Free(PerDoc perDoc)
        {
            lock (this)
            {
                System.Diagnostics.Debug.Assert(freeCount < docFreeList.Length);
                System.Diagnostics.Debug.Assert(0 == perDoc.numStoredFields);
                System.Diagnostics.Debug.Assert(0 == perDoc.fdt.Length);
                System.Diagnostics.Debug.Assert(0 == perDoc.fdt.FilePointer);
                docFreeList[freeCount++] = perDoc;
            }
        }
        
        internal class PerDoc:DocumentsWriter.DocWriter
        {
            public PerDoc(StoredFieldsWriter enclosingInstance)
            {
                InitBlock(enclosingInstance);
            }
            private void  InitBlock(StoredFieldsWriter enclosingInstance)
            {
                this.enclosingInstance = enclosingInstance;
                buffer = enclosingInstance.docWriter.NewPerDocBuffer();
                fdt = new RAMOutputStream(buffer);
            }
            private StoredFieldsWriter enclosingInstance;
            public StoredFieldsWriter Enclosing_Instance
            {
                get
                {
                    return enclosingInstance;
                }
                
            }

            internal DocumentsWriter.PerDocBuffer buffer ;
            internal RAMOutputStream fdt;
            internal int numStoredFields;
            
            internal void  Reset()
            {
                fdt.Reset();
                buffer.Recycle();
                numStoredFields = 0;
            }
            
            public override void  Abort()
            {
                Reset();
                Enclosing_Instance.Free(this);
            }
            
            public override long SizeInBytes()
            {
                return buffer.SizeInBytes;
            }
            
            public override void  Finish()
            {
                Enclosing_Instance.FinishDocument(this);
            }
        }
    }
}